iT邦幫忙

2025 iThome 鐵人賽

DAY 20
1
Modern Web

即時金融數據分析與區塊鏈應用實作:從網頁到計量交易模擬系列 第 20

📈 從日常到金融:計量交易模擬的生活故事

  • 分享至 

  • xImage
  •  

📈 從日常到金融:計量交易模擬的生活故事

🌟 開場:從買早餐到下單股票

早上走進早餐店,老闆娘熟練地拿紙筆記錄訂單,心裡默默在想:「要是能自動幫我算誰先來、誰要加蛋就好了。」
這就像金融市場裡的「即時交易模擬」──前端畫面顯示訂單,後端數據則是金流與庫存,兩邊要互相串接,才不會亂掉。


🔄 即時交易模擬:前端 + 後端數據串接

想像你點一杯珍奶,前端的點單系統馬上秀出「大杯、少冰、半糖」,後端同時減掉一份珍珠庫存。
金融市場裡也是這樣,前端顯示價格走勢,後端立刻更新買賣紀錄,讓模擬跟真實交易一樣快。


📊 計量交易模擬:MACD 自動回測

MACD(指標,用來看趨勢的加減速度)就像早上看天氣 App。
昨天太陽 ☀️,今天下雨 🌧️,你會想:「如果昨天帶傘就好了!」
在交易裡,MACD 可以自動幫你「回測」──模擬昨天若照這個規則下單,今天會賺或賠多少。


⚡ 即時金融 API 串接與前端展示

API(資料高速公路)像是外送平台連到早餐店。
你在 App 上點「蛋餅加大冰紅」,後端廚房馬上收到訊息,螢幕即時更新。
金融交易模擬靠 API 抓價格,前端畫面即時展示,投資人能立刻看到市場跳動。


🔗 Web3 應用:智能合約下單模擬

想像去自助咖啡機,刷卡後機器自動出咖啡,不用店員介入。
Web3 的「智能合約」就是這樣──設定好規則(到價就買、超過就賣),電腦自動幫你下單,不會猶豫也不會忘記。


🤖 即時交易模擬:自動化下單程式

假如你常忘記早餐要點「去冰少糖」,乾脆寫個小紙條,交給朋友每天幫你下單。
交易市場也一樣,可以寫程式模擬「自動下單」,讓系統幫你抓到最佳時機,而不是自己慢慢盯盤。


🧩 計量交易模擬:技術指標優化實作

最後,就像調整珍奶甜度和冰塊比例,你會試很多次才找到「最合適的口味」。
計量交易也要優化技術指標,測試「短天期」還是「長天期」更好,模擬出最貼近自己風格的策略。


🎯 收尾:把金融放回生活

計量交易模擬聽起來很高深,其實就像每天的買早餐、看天氣、外送 App、刷卡咖啡機。
它的核心是:如何用數據和自動化,幫我們減少錯誤、節省時間、抓住機會。
生活中這些小小便利,其實就是金融科技在另一個世界的投影。

🚀「即時」交易模擬:從 0 到可跑的前後端串接(含程式碼)

目標:前端即時顯示價格走勢、下「模擬訂單」,後端接收外部即時行情並提供下單 API(紙上交易)。
技術:Node.js(Express + WebSocket/SSE)、原生 HTML/JS(不依賴框架)。


🧭 架構與資料流

[外部即時行情 (WebSocket)]


[後端 Node.js]

  • 連外部WS拿 tick
  • 轉發為 SSE 或 WS
  • REST: /paper/order


    [前端 HTML/JS]
  • 接 SSE 即時價
  • 即時畫圖 / 報價板
  • 發送模擬下單

⚙️ 後端:Node.js(SSE 版,最穩定好上手)

為了簡化示範,我們直接連到公開的加密貨幣即時價 WebSocket(如 Binance 的 miniTicker),然後把資料轉成 SSE 推給前端。
(若你換其他來源,只要能吐出 {symbol, price, ts} 的 JSON 即可。)

  1. 初始化專案

mkdir rt-trade-sim && cd rt-trade-sim
npm init -y
npm i express ws cors dotenv

  1. 建 .env

PORT=8080

範例:Binance 公開 WS(可改別家或你自建的行情源)

PRICE_WS=wss://stream.binance.com:9443/ws/btcusdt@miniTicker
SYMBOL=BTCUSDT

  1. server.js(SSE + WebSocket 轉發 + 紙上交易下單 API)

// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const WebSocket = require('ws');

const app = express();
app.use(cors());
app.use(express.json());

const PORT = process.env.PORT || 8080;
const PRICE_WS = process.env.PRICE_WS;
const SYMBOL = process.env.SYMBOL || 'BTCUSDT';

// ===== 1) 建立與外部行情的 WebSocket 連線 =====
let priceWs;
let lastPrice = null; // 給 /price 即時取用
let lastTs = null;

// 重連策略
function connectPriceWS() {
priceWs = new WebSocket(PRICE_WS);

priceWs.on('open', () => {
console.log('[WS] Connected to price stream:', PRICE_WS);
});

priceWs.on('message', (raw) => {
try {
const msg = JSON.parse(raw);
// Binance miniTicker: c = close, E = event time
const p = Number(msg.c || msg.price || msg.lastPrice);
const ts = Number(msg.E || msg.T || Date.now());
if (!isNaN(p)) {
lastPrice = p;
lastTs = ts;
// 推給所有 SSE 客戶端
broadcastSSE({ symbol: SYMBOL, price: p, ts });
}
} catch (e) {
// 略過解析錯誤
}
});

priceWs.on('close', () => {
console.log('[WS] Closed. Reconnecting in 2s...');
setTimeout(connectPriceWS, 2000);
});

priceWs.on('error', (err) => {
console.error('[WS] Error:', err.message);
try { priceWs.close(); } catch (_) {}
});
}
connectPriceWS();

// ===== 2) SSE(Server-Sent Events)即時價格推送 =====
const sseClients = new Set();

function broadcastSSE(payload) {
const data = data: ${JSON.stringify(payload)}\n\n;
for (const res of sseClients) {
res.write(data);
}
}

app.get('/price/stream', (req, res) => {
// SSE headers
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
res.flushHeaders();

// 立刻送一筆「當前價」
if (lastPrice !== null) {
res.write(data: ${JSON.stringify({ symbol: SYMBOL, price: lastPrice, ts: lastTs || Date.now() })}\n\n);
}

sseClients.add(res);
req.on('close', () => sseClients.delete(res));
});

// ===== 3) 紙上交易(模擬下單)API =====
/**

  • POST /paper/order
  • body: { side: 'BUY'|'SELL', qty: number, price?: number, type?: 'MARKET'|'LIMIT' }
    • MARKET:用 lastPrice 成交
    • LIMIT:若 price 有利於立刻成交,視同成交;否則回報掛單(示意)
      */
      const paperOrders = []; // 超簡化儲存
      let nextId = 1;

app.post('/paper/order', (req, res) => {
const { side, qty, price, type = 'MARKET' } = req.body || {};
if (!['BUY', 'SELL'].includes(side)) return res.status(400).json({ error: 'side invalid' });
if (typeof qty !== 'number' || qty <= 0) return res.status(400).json({ error: 'qty invalid' });

const now = Date.now();

if (type === 'MARKET') {
if (lastPrice === null) return res.status(503).json({ error: 'no market price' });
const order = {
id: nextId++,
symbol: SYMBOL,
side, type,
qty,
price: lastPrice,
status: 'FILLED',
ts: now,
};
paperOrders.push(order);
return res.json(order);
}

if (type === 'LIMIT') {
if (typeof price !== 'number' || price <= 0) return res.status(400).json({ error: 'price invalid' });
// 簡化:若買單 & 價格 >= 市價 -> 立刻成交;賣單 & 價格 <= 市價 -> 立刻成交
let status = 'NEW';
let execPrice = price;
if (lastPrice !== null) {
if ((side === 'BUY' && price >= lastPrice) || (side === 'SELL' && price <= lastPrice)) {
status = 'FILLED';
execPrice = lastPrice;
}
}
const order = {
id: nextId++,
symbol: SYMBOL,
side, type,
qty,
price: execPrice,
status,
ts: now,
};
paperOrders.push(order);
return res.json(order);
}

return res.status(400).json({ error: 'type invalid' });
});

// 查詢紙上訂單
app.get('/paper/orders', (_req, res) => {
res.json(paperOrders.slice(-50)); // 最近 50 筆
});

// 健康檢查 & 最新價
app.get('/health', (_req, res) => res.json({ ok: true, lastPrice, lastTs }));

app.listen(PORT, () => {
console.log(Server listening on http://localhost:${PORT});
});


🖥️ 前端:原生 HTML/JS(即開即用)

前端用 EventSource (SSE) 接收即時價,並用 畫簡單線圖(無依賴)。提供兩個按鈕快速送出紙上交易(BUY/SELL)。

public/index.html

將此檔放在任意靜態伺服器(或直接開 VSCode Live Server)。若你想同一支 Express 也 serve 前端,就把下方檔案放到 public/,並在 server.js 加 app.use(express.static('public'))。

  1. 讓後端一起服務前端(可選)

把 public/index.html 放到 public/ 資料夾後,在 server.js 加上:

app.use(express.static('public'));


▶️ 啟動

node server.js

開啟 http://localhost:8080

前端會自動連接 /price/stream,畫面開始跑即時價折線


🧪 快速驗證流程

  1. 看得到價:首頁出現最新價、時間戳,折線逐步延伸。

  2. 下市價單:輸入 qty → 點 BUY/SELL(市價),應彈出 FILLED 回應。

  3. 下限價單:輸入 limitPrice,當價格穿越條件時會變 FILLED,否則 NEW。

  4. 看訂單清單:點「重新整理」,出現最近紙上訂單。


🔒 實務注意(最小但關鍵)

別把金鑰放前端:真連交易所或券商 API,金鑰只在後端。

節流/風控:後端應加上頻率限制(Rate Limit)、最大倉位上限、最小間隔等。

重連機制:外部 WS 斷線要自動重連(上面已示範)。

時區/時間序一致:用毫秒 timestamp,同步前後端。

SSE 與 WS 取捨:SSE 穩定、易穿防火牆;WS 雙向、可用於前端直接下單通道(本示範用 REST 下單,更易控管)。


🧱 如果你想換 WebSocket 端到端

將後端開一個 ws://.../price,前端用 new WebSocket() 接收;

下單可改走 WS 傳 {op:'order', ...},但務必做身分驗證與權限控管。

一般建議:行情用 SSE/WS,交易用 REST,分工清晰好維運。


🧩 延伸:把「即時」接上 MACD 與自動化

前端或後端累積價格 → 計算 MACD(12,26,9) → 只要訊號線上穿/下穿,呼叫 /paper/order 建倉/平倉。

建議放在後端做指標與風控(單一真相來源),前端純顯示。


✅ 結語

以上範例就是能跑的「即時交易模擬」最小閉環:
外部即時價 → 後端轉播(SSE/WS) → 前端顯示 → 紙上交易下單 → 訂單回看。
你可以在此基礎上,逐步加入:多商品、倉位管理、手續費滑價、MACD/RSI 訊號、自動化策略排程等。

想像你去早餐店點餐,前台螢幕顯示你的「蛋餅一份、奶茶一杯」(前端 Frontend),後台廚房收到訂單開始煎蛋餅、倒奶茶(後端 Backend)。但這間早餐店的菜價不是自己決定,而是天天去菜市場聽廣播「青菜 30、雞蛋 50」(外部行情 WebSocket)。廚房把聽到的喊價立刻更新到前台螢幕,讓客人看到價格會即時跳動(即時串流 SSE/WS)。當你按下「我要買」或「我要賣」的按鈕,就像在自助點餐機確認餐點(模擬下單 REST API)。如果你選擇「立刻成交」就像用現價直接買早餐(市價單 Market Order),如果你選「等到便宜再買」就像只想用 60 元買雞排(限價單 Limit Order)。整個過程只是在練習,不會真的花錢(紙上交易 Paper Trading),但流程就跟真實餐廳或市場一樣完整。


上一篇
🌐 Web Modern 與即時金融—從 Vibe Coding 的反思到金融服務的挑戰
下一篇
信用卡詐欺偵測的分類(Classification)實戰範例
系列文
即時金融數據分析與區塊鏈應用實作:從網頁到計量交易模擬21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言